import { sound,filters } from "@pixi/sound";
import { ResourceLoader } from "@/utils/ResourceLoader.js";
import { Ticker } from "pixi.js";
export class Audio{
    constructor(type,parent){

        this.parent=parent;
        this.soundObject=null;

        this.isLoading=false;
        this.loadCbs=[];

        this._volume=1;
        this._speed=1;
        this.filter=null;
        this.type=type;

        this.fadeDuration=0;
        this.fadeType=0;//1为in -1为out

        this.fadeCbs=[];
    }

    isReady(){
        return !this.isLoading;
    }
    clear(){
        this.soundObject=null;
    }

    get name(){
        return this.soundObject?.options.name;
    }

    get mastervolume(){
        return this.parent?.volume??1;
    }

    set volume(value){
        this._volume=value;
        if(this.soundObject){
            this.soundObject.volume=value;
        }
    }
    get volume(){
        return this._volume??1;
    }

    set speed(value){
        this._speed=value;
        if(this.soundObject){
            this.soundObject.speed=value;
        }
    }
    get speed(){
        return this._speed??1
    }

    set pan(value){
        if(value==0&&!this.filter)return;
        if(!this.filter){
            this.filter=new filters.StereoFilter(value);
            if(this.soundObject){
                this.soundObject.filters=[this.filter];
            }
        }else{
            this.filter.pan=value;
        }
    }

    get pan(){
        return this.filter?.pan??0;
    }

    play(name){
        if(!name)return;
        if(this.isFading()){
            this.fadePush('play',name);
            return;
        }
        if(this.isLoading){
            this.loadPush('play',name);
            return;
        }

        if(this.soundObject?.name==name){
            if(this.soundObject.paused){
                this.soundObject.resume();
            }else if(!this.soundObject.isPlaying){
                this.soundObject.play(this.makePlayOptions());
            }
            return;
        }
        
        let url=`/audio/${this.type}/${name}.ogg`;
        
        if(window.$dataSystem?.hasEncryptedAudio){
            this.isLoading=true;
            ResourceLoader.loadEncrypterSound(url).then(source=>{
                sound.add(`${name}_${this.type}`,this.makeOptions({
                    source,
                    name
                }));
            });
        }else{
            sound.add(`${name}_${this.type}`,this.makeOptions({
                url,
                name
            }));
        }
    }

    loadPush(method,...args){
        this.loadCbs.push({method,args});
    }

    loadCompleteCall(){
        let cbs=this.loadCbs;
        this.loadCbs=[];
        for(let cb of cbs){
            this[cb.method]?.(...cb.args);
        }
    }

    pause(){
        if(this.isFading()){
            this.fadePush('pause');
            return;
        }
        this.soundObject?.pause();
    }

    fadeIn(duration){
        if(this.isLoading){
            this.loadPush('fadeIn',duration);
            return;
        }

        if(this.isFading()){
            if(this.isFadeOut()){
                this.fadePush('fadeIn',duration);
            }
            return;
        }
        if(this.soundObject){
            if(this.soundObject.paused){
                this.soundObject.resume();
            }else if(!this.soundObject.isPlaying){
                this.soundObject.play(this.makePlayOptions());
            }else{
                return;
            }
            this._volumeLiner(0,this.volume*this.mastervolume,duration);
            this.fadeDuration=duration*60;
            this.fadeType=1;
            Ticker.shared.add(this._updateFade);
        }
    }

    isFadeIn(){
        return this.fadeType==1;
    }
    

    _updateFade=()=>{
        if(this.fadeDuration>0){
            --this.fadeDuration;
        }
        if(this.fadeDuration<=0){
            Ticker.shared.remove(this._updateFade);
            if(this.fadeDuration<=0){
                this.fadeCompleteCall();
            }
            this.fadeType=0;
        }
    }

    fadeOut(duration){
        if(this.isLoading){
            this.loadPush('fadeOut',duration);
            return;
        }

        if(this.isFading()){
            if(this.isFadeIn()){
                this.fadePush('fadeOut',duration);
            }
            return;
        }
        if(this.soundObject&&this.soundObject.isPlaying){
            this._volumeLiner(this.volume*this.mastervolume,0,duration);
            this.fadeDuration=duration*60;
            this.fadeType=1;
            Ticker.shared.add(this._updateFade);
            this.pause();
        }
    }
    isFadeOut(){
        return this.fadeType==-1;
    }
    isFading(){
        return this.fadeDuration>0;
    }

    fadePush(method,...args){
        this.fadeCbs.push({method,args});
    }
    fadeCompleteCall(){
        let cbs=this.fadeCbs;
        this.fadeCbs=[];
        for(let cb of cbs){
            this[cb.method]?.(...cb.args);
        }
    }

    _volumeLiner(from,to,duration){
        this.soundObject.volume=to;
        let currentTime=sound.context.audioContext.currentTime;
        let gain=this.soundObject._instances[0]._gain.gain;
        gain.setValueAtTime(from,currentTime);
        gain.linearRampToValueAtTime(to,currentTime+duration);
    }

    _onload=(err,soundObject)=>{
        this.exchangeSound(soundObject);
        soundObject.play(this.makePlayOptions());
        this.isLoading=false;
        this.loadCompleteCall();
    }

    exchangeSound(soundObject){
        if(this.soundObject){
            if(this.soundObject.isPlaying
                ||this.soundObject.paused){
                this.soundObject.stop();
            }
            sound.remove(`${this.soundObject.options.name}_${this.type}`);
        }
        this.soundObject=soundObject;
    }

    loop(){
        return false;
    }

    isReady(){
        return !this.isLoading;
    }
    
    makeOptions(opts){
        return {
            singleInstance:true,
            preload:true,
            loaded:this._onload,
            ...opts
        }
    }

    makePlayOptions(opts){
        let options={
            volume:this.volume*this.mastervolume,
            speed:this.speed,
            loop:this.loop(),
            ...opts
        };

        if(this.filter){
            options.filters=[this.filter];
        }

        return options;
    }

}